/*
 * lexer.c
 *
 *  Created on: Nov 11, 2010
 *      Author: cpy.prefers.you@gmail.com
 */
 
#include "headers.h"

/* stores current and next token */
Symbol_T *Token0, *Token1;

static char* InputBufferPointer;
static char* InputBufferEnd;
static Symbol_T *InputCache[INPUT_CACHE_SIZE];
static int InputCacheHead = 0;
static int InputCacheTail = 0;

static void Match(char *ExpectedChar)
{
    if (*InputBufferPointer == *ExpectedChar)
        InputBufferPointer++;
    else
        Expected("\"%s\"", ExpectedChar);
    return;
}

static int SkipWhite()
{
    while (IS_WHITE(*InputBufferPointer))
        InputBufferPointer++;
    return 0;
}

static int SkipComment()
{
    while (!(*InputBufferPointer == '*' && *(InputBufferPointer + 1) == '/'))
    {
        if (*InputBufferPointer + 1 == EOF)
            Expected("*/");
        InputBufferPointer++;
    }
    InputBufferPointer += 2;
    SkipWhite();    
    return 0;
}

static int SkipCommentLine()
{
    while (*InputBufferPointer != LF && *InputBufferPointer != EOF)
        InputBufferPointer++;
    if (*InputBufferPointer == LF)
        InputBufferPointer++;
    SkipWhite();                    
    return 0;
}

static int GetIdentifier(char *Identifier)
{
    if (!IS_IDENTIFIER_PREFIX(*InputBufferPointer))
        Expected("Identifier");
    int len = 0;
    while (IS_ALNUM(*InputBufferPointer) || *InputBufferPointer == '_')
    {
        Identifier[len++] = *InputBufferPointer;
        InputBufferPointer++;
    }
    Identifier[len] = 0;
    return TYPE_IDENTIFIER;
}

static int GetNum(char *Num)
{
    char *tmp;
    sprintf(Num, "%ld", strtol(InputBufferPointer, &tmp, 0));
    InputBufferPointer = tmp;
    return TYPE_NUMBER;
} 

static int GetOp(char *Op)
{
    int len = 0;
    char *tmp;
    while (IS_OP(*(InputBufferPointer + len)))
    {
        Op[len] = *(InputBufferPointer + len);
        Op[++len] = 0;
        if (LookupName(Op) != NULL)
            tmp = InputBufferPointer + len;
    }
    Op[tmp - InputBufferPointer] = 0;
    InputBufferPointer = tmp;
    return TYPE_UNKNOWN;
}

static char EscapeChar(char **Char)
{
    char tmp;
    ++*Char;
    tmp = **Char;
    ++*Char;
    return tmp;
}

static int GetChar(char *Char)
{
    Match("\'");
    if (IS_BACKSLASH(*InputBufferPointer))
        Char[0] = EscapeChar(&InputBufferPointer);
    else
    {
        Char[0] = *InputBufferPointer;
        InputBufferPointer++;
    }
    Match("\'");
    Char[1] = 0;
    return TYPE_CHAR;
}

static int GetString(char *String)
{
    Match("\"");
    int len = 0;
    while (!IS_DOUBLE_QUOTATION_MARK(*InputBufferPointer))
    {
        if (IS_BACKSLASH(*InputBufferPointer))
            String[len] = EscapeChar(&InputBufferPointer);
        else
            String[len] = *InputBufferPointer;
        len++;
        InputBufferPointer++;
    }
    String[len] = 0;
    Match("\"");
    return TYPE_STRING;
}

int PutTokenToCache(Symbol_T *TokenBuffer)
{
    InputCache[++InputCacheHead] = TokenBuffer;
    return 0;
}

int GetTokenFromCache(Symbol_T **TokenBuffer)
{
    *TokenBuffer = InputCache[++InputCacheTail];
    if (InputCacheHead == InputCacheTail)
        InputCacheHead = InputCacheTail = 0;
    return 0;
}

/* update Token0 with Token1, then get a new token and store it into Token1 */
int Scan()
{
    //free(Token0);
    Token0 = Token1;
    //Token1 = malloc(sizeof(Symbol_T));
    //Token1->Name = malloc(TOKEN_LEN);
    char Name[TOKEN_LEN];
    int Type;

    if (InputCacheHead)
    {
        GetTokenFromCache(&Token1);
        return 0;
    }

    if (InputBufferPointer >= InputBufferEnd)
    {
        Token1->Type = EOF;
        return 0;    
    }    

    if (IS_IDENTIFIER_PREFIX(*InputBufferPointer))
        Type = GetIdentifier(Name);
    else if (IS_DECIMAL(*InputBufferPointer))
        Type = GetNum(Name);
    else if (IS_OP(*InputBufferPointer))
        Type = GetOp(Name);
    else if (IS_SINGLE_QUOTATION_MARK(*InputBufferPointer))
        Type = GetChar(Name);
    else if (IS_DOUBLE_QUOTATION_MARK(*InputBufferPointer))
        Type = GetString(Name);
    else 
    {
        Name[0] = *InputBufferPointer;
        Name[1] = 0;
        InputBufferPointer++;
    }

    Token1 = LookupName(Name);
    if (!Token1)
    {
        /*
        Token1 = NewSymbol();
        Token1->Type = Type;
        Token1->Name = malloc(MemSize(Type));
        strcpy(Token1->Name, Name);
        InsertSymbol(Token1);
        */
        Token1 = InsertSymbol(&(Symbol_T){ .Name = Name, .Type = Type });
    }

    switch(Token1->Type)
    {
        case TYPE_COMMENT_START:
            SkipComment();
            Token1 = Token0;
            Scan();
            break;
        case TYPE_COMMENT_LINE:
            SkipCommentLine();
            Token1 = Token0;
            Scan();
            break;
        default:
            break;
    }

    SkipWhite();
    return 0;
}

int LexerInit()
{
    InputBufferPointer = malloc(INPUT_BUFFER_SIZE);
    InputBufferEnd = InputBufferPointer;
    int temp;
    while ((temp = fread(InputBufferEnd, 1, INPUT_READ_SIZE, stdin)) != 0)
        InputBufferEnd += temp;    
    
    Token0 = NewSymbol();
    Token1 = NewSymbol();

    /* the following 2 lines, or if in first two call of Scan() there is 
       something with unknown type, the program will raise a segment fault */
    Token0->Name = malloc(TOKEN_LEN);
    Token1->Name = malloc(TOKEN_LEN);

    SkipWhite();        
    Scan();
    Scan();    
    
    return 0;
}
